home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Internet Tools 1993 July / Internet Tools.iso / RockRidge / mail / newsgate / mail2news.c < prev    next >
Encoding:
C/C++ Source or Header  |  1991-07-18  |  15.5 KB  |  676 lines

  1. /*
  2. **  MAIL2NEWS
  3. **  Gateway mail messages into netnews.  Usage:
  4. **    mail2news [inews flags] -o Organization
  5. **  In order to do this, there are a number of interesting transformations
  6. **  that need to be made on the headers...
  7. **
  8. **  This program is descended from:  @(#)recnews.c 2.10 4/16/85.
  9. */
  10. #include "gate.h"
  11. #include <signal.h>
  12. #include <sys/file.h>
  13. #if    defined(RCSID)
  14. static char RCS[] =
  15.     "$Header: /nfs/papaya/u2/rsalz/src/newsgate/RCS/mail2news.c,v 1.19 91/07/18 18:41:45 rsalz Exp $";
  16. #endif    /* defined(RCSID) */
  17.  
  18.  
  19. #define    WAIT_CORED(s)        (s & 0200)
  20. #define    WAIT_EXITSIG(s)        (s & 0177)
  21. #define    WAIT_EXITCODE(s)    ((s >> 8) & 0377)
  22.  
  23. /* For those who don't have this in <sys/file.h>. */
  24. #if    !defined(R_OK)
  25. #define    R_OK            4
  26. #endif    /* !defined(R_OK) */
  27.  
  28. /* Stuff for pipe(2). */
  29. #define STDIN            0
  30. #define    PIPE_READER        0
  31. #define    PIPE_WRITER        1
  32.  
  33.  
  34. /* Global variables. */
  35. STATIC int    Debugging;
  36. STATIC int    ChildPid = -1;
  37. char        *Pname;
  38.  
  39.  
  40.  
  41. /*
  42. **  Go down to the absolute minimum.
  43. */
  44. STATIC void
  45. TrimEnvironment()
  46. {
  47.     static char        *Empty[] = { NULL };
  48.  
  49.     environ = Empty;
  50. }
  51.  
  52.  
  53. /*
  54. **  Quickie hack to see of a mail message is a "please drop me" request.
  55. **  Reads the message on the input file, and returns NULL if it should
  56. **  be ignored, or a FILE handle if we should process it.
  57. **
  58. **  The original stand-alone program written was Russ Nelson,
  59. **  <nelson@clutx.clarkson.edu>.  I hacked on it, and made it into a
  60. **  subroutine.  Perhaps a better way to test is to make the test less
  61. **  conservative, and see what "real" articles get caught, and make
  62. **  adjustments then?  Comments solicited.
  63. */
  64. STATIC FILE *
  65. IsSubRequest(F)
  66.     register FILE    *F;
  67. {
  68.     register FILE    *Out;
  69.     register char    *p;
  70.     register int    c;
  71.     register int    drop_or_add;
  72.     register int    from_or_to;
  73.     register int    mail_word;
  74.     register int    count;
  75.     char        word[SM_SIZE];
  76.     char        buff[SM_SIZE];
  77.  
  78.     /* Create temp file; if we can't let the message through. */
  79.     if ((Out = fopen(mktemp(strcpy(buff, TEMPFILE)), "w")) == NULL)
  80.     return F;
  81.  
  82.     /* Clear counts. */
  83.     drop_or_add = 0;
  84.     from_or_to = 0;
  85.     mail_word = 0;
  86.     count = 0;
  87.  
  88.     /* Read input a word at a time. */
  89.     for (p = word; (c = getc(F)) != EOF; ) {
  90.     (void)putc(c, Out);
  91.     if (!isalpha(c)) {
  92.         *p = '\0';
  93.         if (p > word)
  94.         count++;
  95.         p = word;
  96.  
  97.         if (EQ(word, "remove") || EQ(word, "drop") || EQ(word, "off")
  98.          || EQ(word, "subscribe") || EQ(word, "get") || EQ(word, "add"))
  99.         drop_or_add++;
  100.         else if (EQ(word, "from") || EQ(word, "to"))
  101.         from_or_to++;
  102.         else if (EQ(word, "mail") || EQ(word, "mailing")
  103.           || EQ(word, "list") || EQ(word, "dl"))
  104.         mail_word++;
  105.     }
  106.     else if (p < &word[sizeof word - 1])
  107.         *p++ = isupper(c) ? tolower(c) : c;
  108.     }
  109.  
  110.     (void)fclose(F);
  111.     (void)fclose(Out);
  112.  
  113.     /* Use fancy-shmancy AI techniques to determine what the message is. */
  114.     c = count < 25 && drop_or_add && from_or_to && mail_word;
  115.     F = c ? NULL : fopen(buff, "r");
  116.  
  117.     (void)unlink(buff);
  118.     return F;
  119. }
  120.  
  121.  
  122. /*
  123. **  Modify the Newsgroups: as directed by the command string.
  124. */
  125. STATIC void
  126. DoCommand(hp, command, group)
  127.     register HBUF        *hp;
  128.     char            *command;
  129.     char            *group;
  130. {
  131.     register char        *p;
  132.     register int        i;
  133.     register int        n;
  134.     register int        nng;
  135.     char            **tokens;
  136.     char            **ng;
  137.     char            buff[BUFSIZ];
  138.  
  139.     if ((n = Split(command, &tokens, '\0')) == 0) {
  140.     SplitFree(&tokens);
  141.     return;
  142.     }
  143.  
  144.     nng = Split(hp->nbuf, &ng, NGDELIM);
  145.     p = hp->nbuf;
  146.     switch (tokens[0][0]) {
  147.     case 'a':                /* Add        */
  148.     if (n > 1)
  149.         for (p += strlen(p), i = 1; i < n; i++) {
  150.         *p++ = NGDELIM;
  151.         p += APPEND(p, tokens[i]);
  152.         }
  153.     break;
  154.     case 'd':                /* Delete    */
  155.     for (i = 0; i < nng; i++)
  156.         if (!EQ(ng[i], group)) {
  157.         if (p > hp->nbuf)
  158.             *p++ = NGDELIM;
  159.         p += APPEND(p, ng[i]);
  160.         }
  161.     if (p == hp->nbuf)
  162.         Strcpy(hp->nbuf, "junk");
  163.     break;
  164.     case 'k':                /* Kill        */
  165.     Fprintf(stderr, "%s:  Your posting to %s was killed by %s.\n",
  166.         Pname, hp->nbuf, n > 1 ? tokens[1] : group);
  167.     exit(EX_NOPERM);
  168.     /* NOTREACHED */
  169.     case 'm':                /* Move        */
  170.     if (n > 1)
  171.         if (nng == 1)
  172.         Strcpy(hp->nbuf, tokens[1]);
  173.         else
  174.         for (i = 0; i < nng; i++) {
  175.             if (p > hp->nbuf)
  176.             *p++ = NGDELIM;
  177.             p += APPEND(p, EQ(ng[i], group) ? tokens[1] : ng[i]);
  178.         }
  179.     break;
  180.     case 'q':                /* Quiet kill    */
  181.     if (Debugging) {
  182.         (void)printf("Quiet kill (ignored for debugging).\n");
  183.         break;
  184.     }
  185.     /* Eat the message up, and pretend we delivered it. */
  186.     while (fgets(buff, sizeof buff, stdin))
  187.         continue;
  188.     exit(EX_OK);
  189.     /* NOTREACHED */
  190.     }
  191.  
  192.     SplitFree(&tokens);
  193.     SplitFree(&ng);
  194. }
  195.  
  196.  
  197. /*
  198. **  For Ozan Yigit's public domain regex.
  199. */
  200. /* ARGSUSED */
  201. void
  202. re_fail(text, arg)
  203.     char    *text;
  204.     int        arg;
  205. {
  206. }
  207.  
  208.  
  209. /*
  210. **  Split a line that looks like XpatternXcommandX into the pattern and
  211. **  the command.  Initialize the RE matcher with the pattern, and return
  212. **  the command.
  213. */
  214. STATIC char *
  215. ParsePattern(p, lineno)
  216.     register char    *p;
  217.     int            lineno;
  218. {
  219.     register char    *cp;
  220.     register char    *command;
  221.     register char    delim;
  222.     char        *RE;
  223.  
  224.     /* Ignore comments and blank lines. */
  225.     if (*p == '#' || *p == '\0')
  226.     return NULL;
  227.  
  228.     for (delim = *p++, RE = cp = p, command = NULL; *cp; *p++ = *cp++)
  229.     if (*cp == '\\' && cp[1] == delim)
  230.         cp++;
  231.     else if (*cp == delim) {
  232.         /* Found delimiter; mark command, terminate RE. */
  233.         command = ++cp;
  234.         *p = '\0';
  235.         break;
  236.     }
  237.  
  238.     if (command == NULL || *command == '\0')
  239.     Fprintf(stderr, "%s:  Incomplete regular expression, line %d.\n",
  240.         Pname, lineno);
  241.     else if ((cp = re_comp(RE)) != NULL)
  242.     Fprintf(stderr, "%s:  Bad regular expression, line %d: %s.\n",
  243.         Pname, lineno, cp);
  244.     else
  245.     return command;
  246.  
  247. #if    defined(lint)
  248.     /* My, my, aren't we anal. */
  249.     (void)re_subs("", "");
  250.     re_modw("");
  251. #endif    /* defined(lint) */
  252.  
  253.     return NULL;
  254. }
  255.  
  256.  
  257. /*
  258. **  Convert string to lower case, return a new copy.
  259. */
  260. STATIC char *
  261. MakeLowerCopy(s)
  262.     register char    *s;
  263. {
  264.     register char    *p;
  265.  
  266.     for (p = s = COPY(s); *p; p++)
  267.     if (isupper(*p))
  268.         *p = tolower(*p);
  269.     return s;
  270. }
  271.  
  272.  
  273. /*
  274. **  Free up something that ReadFile made.
  275. */
  276. void
  277. FreeFile(V)
  278.     char        **V;
  279. {
  280.     register char    **p;
  281.  
  282.     if ((p = V) != NULL) {
  283.     while (*p)
  284.         free(*p++);
  285.     free((char *)V);
  286.     }
  287. }
  288.  
  289.  
  290. /*
  291. **  Change newsgroups if the Subject:, Keywords:, or Summary: match a
  292. **  pattern found in the newsgroup remap file.
  293. */
  294. STATIC void
  295. Editnewsgroups(hp)
  296.     register HBUF        *hp;
  297. {
  298.     register char        *p;
  299.     register int        n;
  300.     register int        i;
  301.     register int        j;
  302.     register int        t;
  303.     char            **groups;
  304.     char            **mapline;
  305.     char            *hdrline[4];
  306.     char            buff[LG_SIZE];
  307.  
  308.     /* Copy some headers, but if nothing's there, give up. */
  309.     i = 0;
  310.     if (hp->title[0])
  311.     hdrline[i++] = MakeLowerCopy(hp->title);
  312.     if (hp->keywords[0])
  313.     hdrline[i++] = MakeLowerCopy(hp->keywords);
  314.     if (hp->summary[0])
  315.     hdrline[i++] = MakeLowerCopy(hp->summary);
  316.     if (i == 0)
  317.     return;
  318.     hdrline[i] = NULL;
  319.  
  320.     /* For all the newsgroups, see if there's a mapping file. */
  321.     for (n = Split(hp->nbuf, &groups, NGDELIM), i = 0; i < n; i++) {
  322.     if (groups[i] == NULL || groups[i][0] == '\0')
  323.         continue;
  324.  
  325.     /* Gate the name of the mapping file. */
  326. #if    defined(IN_ONEPLACE)
  327.     Strcpy(buff, IN_ONEPLACE);
  328. #endif    /* defined(IN_ONEPLACE) */
  329. #if    defined(IN_SPOOLDIR)
  330.     {
  331.         register char    *q;
  332.  
  333.         for (p = buff + APPEND(buff, IN_SPOOLDIR), q = groups[i]; *q; q++)
  334.         *p++ = *q == '.' ? '/' : *q;
  335.         Strcpy(p, "/recnews.cmd");
  336.     }
  337. #endif    /* defined(IN_SPOOLDIR) */
  338. #if    defined(IN_CMDDIR)
  339.     Sprintf(buff, "%s/%s", IN_CMDDIR, groups[i]);
  340. #endif    /* defined(IN_CMDDIR) */
  341.  
  342.     if (access(buff, R_OK) >= 0 && (mapline = ReadFile(buff))) {
  343.         /* For all lines in the file, if there's a command and the
  344.          * pattern matches, execute the command. */
  345.         for (j = 0; mapline[j]; j++)
  346.         if ((p = ParsePattern(mapline[j], j)) != NULL)
  347.             for (t = 0; hdrline[t]; t++)
  348.             if (re_exec(hdrline[t]) == 1) {
  349.                 DoCommand(hp, p, groups[i]);
  350.                 break;
  351.             }
  352.         FreeFile(mapline);
  353.     }
  354.     }
  355.  
  356.     /* Free dynamic space. */
  357.     for (i = 0; hdrline[i]; i++)
  358.     free(hdrline[i]);
  359.     SplitFree(&groups);
  360. }
  361.  
  362.  
  363. /*
  364. **  Signal-catcher and child-reapers.
  365. */
  366.  
  367.  
  368. /*
  369. **  Exit such that sendmail will again later.
  370. */
  371. STATIC CATCHER
  372. Sig_tempfail()
  373. {
  374.     exit(EX_TEMPFAIL);
  375. }
  376.  
  377.  
  378. /*
  379. **  Reap the inews child properly, and exit with his exit code, so that
  380. **  ultimate success or failure rests with inews.
  381. */
  382. STATIC CATCHER
  383. childgone()
  384. {
  385.     register int    pid;
  386.     int            W;
  387.  
  388.     /* Some systems get race conditions, causing this routine to be
  389.      * invoke multiple times, sigh.  Some systems want SIG_IGN, I think. */
  390. #if    defined(SIGCHLD)
  391.     Signal(SIGCHLD, SIG_DFL);
  392. #endif    /* defined(SIGCHLD) */
  393. #if    defined(SIGCLD)
  394.     Signal(SIGCLD, SIG_DFL);
  395. #endif    /* defined(SIGCLD) */
  396.  
  397.     if ((pid = wait(&W)) != ChildPid || pid == -1)
  398.     exit(EX_OSERR);
  399.     
  400.     /* Was it a good death? */
  401.     if (WAIT_EXITSIG(W)) {
  402.     Fprintf(stderr, "%s:  Child %d killed by signal %d.\n",
  403.         Pname, pid, WAIT_EXITSIG(W));
  404.     if (WAIT_CORED(W))
  405.         Fprintf(stderr, "%s:  Child %d dumped core.\n", Pname, pid);
  406.     exit(EX_SOFTWARE);
  407.     }
  408.  
  409. #if    defined(MMDF)
  410.     /* We need a way to tell temporary errors from permanent ones.  Inews
  411.      * will reject messages because of too much quoting, for example,
  412.      * so the message will sit in the queue forever.  Until then we'll
  413.      * have to lose messages on any error. */
  414.     exit(0);
  415. #else
  416.     exit(WAIT_EXITCODE(W));
  417. #endif    /* defined(MMDF) */
  418. }
  419.  
  420.  
  421.  
  422. /*
  423. **  Convert the characters following dots to upper case, if they're
  424. **  lower case.  Two dots in a row will leave one dot in their place.
  425. **  Modifies the argument.
  426. */
  427. STATIC char *
  428. HackPeriods(string)
  429.     char        *string;
  430. {
  431.     register char    *s;
  432.     register char    *p;
  433.  
  434.     if (string) {
  435.     for (p = s = string; *p; *s++ = *p++)
  436.         if (*p == '.') {
  437.         if (*++p == '\0') {
  438.             *s++ = '.';
  439.             break;
  440.         }
  441.         if (islower(*p))
  442.             *p = toupper(*p);
  443.         }
  444.     *s = '\0';
  445.     }
  446.     return string;
  447. }
  448.  
  449.  
  450.  
  451. main(ac, av)
  452.     register int    ac;
  453.     register char    *av[];
  454. {
  455.     register char    **vec;
  456.     register char    *p;
  457.     register FILE    *F;
  458.     register FILE    *Infile;
  459.     HBUF        H;
  460.     char        **iv;
  461.     char        buff[BUFSIZ];
  462. #if    defined(SENDMAIL)
  463.     char        sendbuff[sizeof buff];
  464. #endif    /* defined(SENDMAIL) */
  465.     int            fd[2];
  466.     int            FlushSubRequests;
  467.     int            SubjectRequired;
  468.     int            GotEquals;
  469.  
  470.     if ((Pname = RDX(av[0], '/')) == NULL)
  471.     Pname = av[0];
  472.     else
  473.     Pname++;
  474.     Infile = stdin;
  475.  
  476.     /* Remove any trace of who we are. */
  477.     TrimEnvironment();
  478.  
  479.     /* So that cores will actually drop... */
  480.     if (chdir("/tmp") < 0) {
  481.     Fprintf(stderr, "%s:  Can't chdir(/tmp), %s.\n", Pname,
  482.         strerror(errno));
  483.     exit(EX_TEMPFAIL);
  484.     }
  485.  
  486.     /* If someone wants to shut down the system, tell sendmail to
  487.      * try again later. */
  488.     Signal(SIGTERM, Sig_tempfail);
  489.  
  490. #if    defined(SENDMAIL)
  491.     /* First read should fetch us the UNIX From_ line.  Not done in MMDF. */
  492.     if (fgets(buff, sizeof buff, Infile) == NULL)
  493.     exit(EX_NOINPUT);
  494.  
  495. #if    defined(REQUIRE_UNIX_FROM)
  496.     if (!EQn(buff, "From ", 5)) {
  497.     Fprintf(stderr, "%s:  Input didn't start with UNIX From line:\n",
  498.         Pname);
  499.     Fprintf(stderr,"\t%s.\n", buff);
  500.     exit(EX_DATAERR);
  501.     }
  502. #endif    /* defined(REQUIRE_UNIX_FROM) */
  503.  
  504.     /* Turn "From foo ... remote from bar ..." into "Sender: bar!foo". */
  505.     if (EQn(buff, "From ", 5)) {
  506.     Strcpy(sendbuff, "Sender: ");
  507.     for (p = buff + 4; (p = IDX(p + 1, 'r')) != NULL; )
  508.         if (strncmp(p, "remote from ", 12) == 0
  509.          && sscanf(p, "remote from %s", sendbuff + 8) == 1) {
  510.         Strcat(sendbuff, "!");
  511.         break;
  512.         }
  513.     if (sscanf(buff, "From %s", sendbuff + strlen(sendbuff)) == 1)
  514.         Strcpy(buff, sendbuff);
  515.     }
  516. #endif    /* defined(SENDMAIL) */
  517.  
  518.     /* Read the mail header. */
  519.     rfc822read(&H, Infile, buff, sizeof buff);
  520.  
  521.     /* Process the argument list, copying anything that we don't recognize
  522.      * over to the inews argument list and changing things as we see fit. */
  523.     FlushSubRequests = FALSE;
  524.     GotEquals = FALSE;
  525.     SubjectRequired = TRUE;
  526.     iv = NEW(char*, ac+ 2);
  527.     iv[0] = INEWS;
  528.     iv[1] = "-h";
  529.     for (vec = iv + 2; (p = *++av) != NULL; )
  530.     if (p[0] != '-')
  531.         *vec++ = p;
  532.      else
  533.         switch(p[1]) {
  534.         case 'x':
  535.         SubjectRequired = FALSE;
  536.         /* FALLTHROUGH */
  537.         default:
  538.         *vec++ = p;
  539.         break;
  540.         case '=':
  541.         if (!GotEquals)
  542.             iv++;
  543.         iv[0] = p[2] ? &p[2] : *++av;
  544.         GotEquals++;
  545.         break;
  546.         case '.':
  547.         Debugging++;
  548.         break;
  549.         case 'n':
  550.         /* Newsgroup this messages goes to. */
  551.         Strcpy(H.nbuf, p[2] ? &p[2] : *++av);
  552.         break;
  553.         case 'o':
  554.         /* Default organization. */
  555.         p = p[2] ? &p[2] : *++av;
  556.         if (H.organization[0] == '\0')
  557.             Strcpy(H.organization, HackPeriods(p));
  558.         SubjectRequired = FALSE;
  559.         break;
  560.         case 'd':
  561.         /* Default distribution. */
  562.         p = p[2] ? &p[2] : *++av;
  563.         if (H.distribution[0] == '\0')
  564.             Strcpy(H.distribution, p);
  565.         break;
  566.         case 'a':
  567.         /* Default approval. */
  568.         p = p[2] ? &p[2] : *++av;
  569.         if (H.approved[0] == '\0')
  570.             Strcpy(H.approved, p);
  571.         break;
  572.         case 'F':
  573.         FlushSubRequests = TRUE;
  574.         break;
  575.         }
  576.     *vec++ = NULL;
  577.  
  578.     /* Bash on the mail header. */
  579.     if ((p = HackHeader(&H, SubjectRequired)) != NULL) {
  580.     Fprintf(stderr, "%s:  Rejected by netnews because:\n", Pname);
  581.     Fprintf(stderr, "\t%s.\n", p);
  582.     if (H.nbuf[0])
  583.         Fprintf(stderr, "\tIt was going into the newsgroup%s %s.\n",
  584.         IDX(H.nbuf, NGDELIM) ? "s" : "", H.nbuf);
  585.     exit(EX_DATAERR);
  586.     }
  587.     Editnewsgroups(&H);
  588.  
  589.     if (Debugging) {
  590.     for (vec = iv; *vec; vec++)
  591.         (void)printf(" |%s| ", *vec);
  592.     (void)printf("\n");
  593.     if (!rfc822write(&H, stdout))
  594.         Fprintf(stderr, "%s:  Can't write header, %s.\n",
  595.         Pname, strerror(errno));
  596.     while (fgets(buff, sizeof buff, Infile))
  597.         Fputs(buff, stdout);
  598.     exit(EX_OK);
  599.     }
  600.  
  601.     if (FlushSubRequests && (Infile = IsSubRequest(Infile)) == NULL) {
  602.     Fprintf(stderr, "%s:  Rejected by netnews becase:\n", Pname);
  603.     Fprintf(stderr, "\tIt seems like a subscription request.\n");
  604.     exit(EX_DATAERR);
  605.     }
  606.  
  607.     /* Get ready to spawn an inews. */
  608.     if (pipe(fd) < 0) {
  609.     Fprintf(stderr, "%s:  Can't pipe, %s.\n", Pname, strerror(errno));
  610.     exit(EX_TEMPFAIL);
  611.     }
  612.     Fflush(stderr);
  613.     Fflush(stdout);
  614. #if    defined(SIGCHLD)
  615.     Signal(SIGCHLD, childgone);
  616. #endif    /* defined(SIGCHLD) */
  617. #if    defined(SIGCLD)
  618.     Signal(SIGCLD, childgone);
  619. #endif    /* defined(SIGCLD) */
  620.  
  621.     if ((ChildPid = fork()) < 0) {
  622.     Fprintf(stderr,"%s:  Can't fork, %s.\n", Pname, strerror(errno));
  623.     exit(EX_TEMPFAIL);
  624.     }
  625.     if (ChildPid == 0) {
  626.     /* Redirect I/O; it's unlikely the test below will fail. */
  627.     if (fd[PIPE_READER] != STDIN) {
  628.         Close(STDIN);
  629.         if (dup(fd[PIPE_READER]) != STDIN)
  630.         Fprintf(stderr, "%s:  Can't redirect input, %s.\n",
  631.             Pname, strerror(errno));
  632.     }
  633.     Close(fd[PIPE_READER]);
  634.     Close(fd[PIPE_WRITER]);
  635.     (void)execv(iv[0], iv);
  636.     Fprintf(stderr, "%s:  Can't exec %s, %s.\n",
  637.         Pname, iv[0], strerror(errno));
  638.     exit(EX_OSERR);
  639.     }
  640.  
  641.     /* Set things up after the fork. */
  642.     Close(fd[PIPE_READER]);
  643.     Signal(SIGPIPE, childgone);
  644.     if ((F = fdopen(fd[PIPE_WRITER], "w")) == NULL)
  645.     exit(EX_OSERR);
  646.  
  647.     /* Stuff the header. */
  648.     if (!rfc822write(&H, F)) {
  649.     Fprintf(stderr, "%s:  Can't write header, %s.\n",
  650.         Pname, strerror(errno));
  651.     exit(EX_IOERR);
  652.     }
  653.  
  654.     /* Write the rest of the message. */
  655.     while (fgets(buff, sizeof buff, Infile)) {
  656.     Fputs(buff, F);
  657.     if (ferror(F))
  658.         break;
  659.     }
  660.  
  661.     /* Close down the pipe. */
  662.     Fflush(F);
  663.     if (ferror(F)) {
  664.     Fprintf(stderr, "%s:  Error flushing pipe to news, %s.\n",
  665.         Pname, strerror(errno));
  666.     exit(EX_IOERR);
  667.     }
  668.     if (fclose(F) == EOF)
  669.     Fprintf(stderr, "%s:  Error closing pipe to news, %s.\n",
  670.         Pname, strerror(errno));
  671.  
  672.     /* Wait for inews, and exit as it does. */
  673.     childgone();
  674.     /* NOTREACHED */
  675. }
  676.